#!/usr/bin/env python3
"""
演示 PEP 517 构建后端如何传递命令行参数给 setup.py
重点：setuptools.build_meta 中 exec(code, locals()) 的 sys.argv 处理
"""

import sys
import os
import tempfile
import textwrap

def demo_pep517_argv_mechanism():
    """演示 PEP 517 构建后端的 sys.argv 处理机制"""
    
    print("=" * 70)
    print("🔍 PEP 517 构建后端 sys.argv 传递机制演示")
    print("=" * 70)
    
    print("\n问题：当 setuptools.build_meta 用 exec(code, locals()) 执行 setup.py 时，")
    print("      setup.py 中的 setup() 函数如何获得命令行参数？")
    
    print("\n💡 答案：build_meta 会主动设置 sys.argv！")

def show_real_mechanism():
    """展示真实的机制"""
    
    print("\n🛠️  setuptools.build_meta 的实际工作流程:")
    print("-" * 50)
    
    mechanism_code = '''
# 简化版的 setuptools.build_meta 逻辑
import sys
import os

def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
    """PEP 517 构建入口点"""
    
    # 1. 保存原始的 sys.argv
    original_argv = sys.argv.copy()
    
    try:
        # 2. 设置模拟的命令行参数
        #    这里是关键：build_meta 会根据构建需要设置 sys.argv
        sys.argv = ['setup.py', 'bdist_wheel', '--dist-dir', wheel_directory]
        
        # 3. 读取 setup.py 内容
        with open('setup.py', 'r') as f:
            setup_code = f.read()
        
        # 4. 准备执行环境
        __file__ = os.path.abspath('setup.py')
        __name__ = '__main__'
        
        # 5. 执行 setup.py - 关键步骤！
        #    setup.py 中的代码会看到我们设置的 sys.argv
        exec(setup_code, {
            '__file__': __file__,
            '__name__': __name__,
            # ... 其他必要的全局变量
        })
        
        # 6. 收集构建结果
        return find_wheel_file(wheel_directory)
        
    finally:
        # 7. 恢复原始的 sys.argv
        sys.argv = original_argv
    '''
    
    print(mechanism_code)

def demo_with_simulation():
    """通过模拟演示这个过程"""
    
    print("\n🎭 模拟演示:")
    print("-" * 30)
    
    # 创建一个简单的 setup.py 内容
    setup_py_content = '''
import sys
print(f"setup.py 中看到的 sys.argv: {sys.argv}")

def setup(**kwargs):
    print(f"setup() 函数被调用，参数: {list(kwargs.keys())}")
    
    # setup() 函数可以直接访问 sys.argv
    if len(sys.argv) > 1:
        command = sys.argv[1]
        print(f"检测到命令: {command}")
        
        if command == 'bdist_wheel':
            print("执行 wheel 构建逻辑")
        elif command == 'build':
            print("执行普通构建逻辑")
        elif command == 'develop':
            print("执行开发模式安装逻辑")
    
    return "wheel-filename.whl"

# 调用 setup 函数
result = setup(name="test", version="1.0")
print(f"构建结果: {result}")
'''
    
    print("1. 模拟的 setup.py 内容:")
    print(textwrap.indent(setup_py_content, "   "))
    
    print("\n2. 模拟 build_meta.build_wheel() 调用:")
    
    # 模拟 build_meta 的行为
    def mock_build_meta_build_wheel():
        print("   a) 保存原始 sys.argv:", sys.argv)
        
        # 模拟设置 sys.argv
        original_argv = sys.argv.copy()
        sys.argv = ['setup.py', 'bdist_wheel', '--dist-dir', './dist']
        print("   b) 设置模拟 sys.argv:", sys.argv)
        
        try:
            # 模拟 exec(setup_code, locals())
            print("   c) 执行 setup.py 代码:")
            print("   " + "-" * 40)
            
            __file__ = os.path.abspath('setup.py')
            __name__ = '__main__'
            
            # 执行模拟的 setup.py 内容
            exec(setup_py_content, {
                '__file__': __file__,
                '__name__': __name__,
                '__builtins__': __builtins__
            })
            
            print("   " + "-" * 40)
            
        finally:
            sys.argv = original_argv
            print("   d) 恢复原始 sys.argv:", sys.argv)
    
    mock_build_meta_build_wheel()

def show_different_commands():
    """展示不同构建命令的 sys.argv 设置"""
    
    print("\n📋 不同构建场景的 sys.argv 设置:")
    print("-" * 40)
    
    scenarios = [
        ("build_wheel", ['setup.py', 'bdist_wheel', '--dist-dir', './dist']),
        ("build_sdist", ['setup.py', 'sdist', '--dist-dir', './dist']),  
        ("build_editable", ['setup.py', 'develop']),
        ("get_requires", ['setup.py', '--name']),  # 只是获取信息
    ]
    
    for scenario, argv in scenarios:
        print(f"   {scenario:15} -> sys.argv = {argv}")

def explain_your_case():
    """解释你的具体情况"""
    
    print("\n🎯 你的项目中的具体情况:")
    print("-" * 35)
    
    print("""
    当你运行 'pip install -e . -v' 时：
    
    1️⃣  pip 检测到 pyproject.toml:
        [build-system]
        build-backend = "setuptools.build_meta"
    
    2️⃣  pip 调用 setuptools.build_meta.build_editable():
        - 这是 PEP 517 的标准接口
        - 用于创建可编辑安装的 wheel
    
    3️⃣  build_meta.build_editable() 内部:
        # 保存原始 sys.argv
        original_argv = sys.argv
        
        # 设置模拟的命令行参数
        sys.argv = ['setup.py', 'develop']  # 或类似的
        
        try:
            # 读取并执行 setup.py
            with open('setup.py') as f:
                code = f.read()
            exec(code, locals())  # ← 这里！
        finally:
            sys.argv = original_argv
    
    4️⃣  你的 setup.py 中的 setup() 函数:
        - 可以正常访问 sys.argv
        - 看到的是 build_meta 设置的模拟参数
        - 不是原始的 'pip install -e . -v'
    
    🔑 关键点：
       ✓ sys.argv 被 build_meta 主动设置
       ✓ setup.py 看到的是构建相关的命令
       ✓ 不需要额外的参数传递机制
    """)

def main():
    demo_pep517_argv_mechanism()
    show_real_mechanism()
    demo_with_simulation()
    show_different_commands()
    explain_your_case()
    
    print("\n" + "=" * 70)
    print("✅ 总结：")
    print("   setuptools.build_meta 通过主动设置 sys.argv 来传递命令参数，")
    print("   setup.py 中的代码通过 exec() 执行时能正常访问这些参数。")
    print("   这是一个优雅的设计，保持了与传统 setup.py 调用的兼容性。")
    print("=" * 70)

if __name__ == "__main__":
    main() 